package cyb;

import cn.hutool.core.io.FileUtil;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class Util {

    //地球平均半径  
    private static final double EARTH_RADIUS = 6378137;

    //把经纬度转为度（°）
    private static double rad(double d) {
        return d * Math.PI / 180.0;
    }


    public static long costTime = 0; //交集时间
    public static long costNum = 0;
    public static long costOderStringTime = 0;//字符串排序
    public static long costOderStringNum = 0;//字符串排序
    public static long costIsFrequentTime = 0;//判断频繁
    public static long costIsFrequentNum = 0;//判断频繁

    /**
     * 用于join-less中的coMap的表实例添加
     */
    public static void coMapAdd(Map<String, List<String>> nCoMap, String nCoType, String nCo) {
        List<String> nCoList = nCoMap.get(nCoType);
        if (nCoList == null) {
            nCoList = new LinkedList<>();
            nCoList.add(nCo);
            nCoMap.put(nCoType, nCoList);
        } else {
            nCoList.add(nCo);
        }
    }

    public static boolean compareDiff(Set<String> set1, Set<String> set2, boolean printDetail) {
        Set<String> result = new HashSet<String>();
        if (printDetail) {
            System.out.println("set1大小：" + set1.size() + " set2大小：" + set2.size());
            System.out.println("set1 = " + set1.toString());
            System.out.println("set2 = " + set2.toString());
        }
        result.clear();
        result.addAll(set1);
        result.retainAll(set2);
//        System.out.println("交集：" + result);

        Set<String> result2 = new HashSet<String>();
        result2.addAll(set1);
        result2.addAll(set2);
//        System.out.println("并集：" + result2);

        result2.removeAll(result);
        if (result2.size() == 0) {
            return false;
        }
        System.out.println("差集：" + result2);

        result.clear();
        result.addAll(set1);
        result.removeAll(set2);
        System.out.println("在1不在2的差集：" + result);

        result.clear();
        result.addAll(set2);
        result.removeAll(set1);
        System.out.println("在2不在1的差集：" + result);
        return true;
    }

    /**
     * 根据两点间经纬度坐标（double值），计算两点间距离，单位为米
     *
     * @param lng1 经度
     * @param lat1 纬度
     */
    public static double getDistance(double lng1, double lat1, double lng2, double lat2) {
        double radLat1 = rad(lat1);
        double radLat2 = rad(lat2);
        double a = radLat1 - radLat2;
        double b = rad(lng1) - rad(lng2);
        double s = 2 * Math.asin(
                Math.sqrt(
                        Math.pow(Math.sin(a / 2), 2)
                                + Math.cos(radLat1) * Math.cos(radLat2) * Math.pow(Math.sin(b / 2), 2)
                )
        );
        s = s * EARTH_RADIUS;
        s = Math.round(s * 10000) / 10000;
        return s;
    }

    public static double getEarthDistance(double[] p1, double[] p2) {
        double lng1 = p1[0];
        double lng2 = p2[0];
        double lat1 = p1[1];
        double lat2 = p2[1];
        return getDistance(lng1, lat1, lng2, lat2);
    }

    // 经纬度覆盖范围： (115.775922,40.441686)(117.502298,41.044505)
    // 因为纬度不同时，1个经度差的距离会变，所以采用数据集中 最低纬度40.441686时的1个经度差的距离 84721.0 用于距离转经纬度
    public static double getlngBydistance(double distance) {
        return distance / 84721.0;
    }

    //一个纬度差不随 经度变化
    public static double getlatBydistance(double distance) {
        return distance / 111319;
    }


    public static Double int2double(int a) {
        return Double.valueOf(String.valueOf(a));
    }

    public static void main(String[] args) {

        //经度相同，随着纬度越大，纬度差的距离不变
        double distance1 = getDistance(0, 1, 0, 2); // 111319
        double distance2 = getDistance(80, 1, 80, 2); // 111319
        System.out.println(distance1);
        System.out.println(distance2);

        //纬度相同，但随着纬度越大，一个经度差的距离越小
        double distance3 = getDistance(1, 0, 2, 0); //111319.0
        System.out.println(distance3);
        double distance4 = getDistance(1, 80, 2, 80); //19330.0
        double distance5 = getDistance(1, 40.441686, 2, 40.441686); //84721.0
        System.out.println(distance4);
        System.out.println(distance5);
    }


    public static Set<String> intersect(Set<String> a, Set<String> b) {
        int aSize = a.size();
        int bSize = b.size();

        long start = System.currentTimeMillis();
        Set<String> intersect = new HashSet<>();
        if (aSize <= bSize) {
            intersect.addAll(a);
            intersect.retainAll(b);
        } else {
            intersect.addAll(b);
            intersect.retainAll(a);
        }

        costTime += System.currentTimeMillis() - start;
        costNum++;
        return intersect;
    }

    public static int[][] load(Set<String> graph) {


        final Map<Integer, Set<Integer>> idToNeighbors = new HashMap();

        int maxNum = 0;
        for (String line : graph) {
            if (line == null) {
                break;
            } else {
                String[] tokens = line.split(",");
                int src = Integer.parseInt(tokens[0]);
                int trg = Integer.parseInt(tokens[1]);

                if (src == trg)
                    continue;

                if (!idToNeighbors.containsKey(src)) {
                    idToNeighbors.put(src, new TreeSet());
                }
                idToNeighbors.get(src).add(trg);
                if (!idToNeighbors.containsKey(trg)) {
                    idToNeighbors.put(trg, new TreeSet());
                }
                idToNeighbors.get(trg).add(src);
                maxNum = Math.max(src, maxNum);
                maxNum = Math.max(trg, maxNum);
            }
        }

        int[][] results = new int[maxNum + 1][];
        for (int i = 0; i < results.length; i++) {
            if (idToNeighbors.containsKey(i)) {
                results[i] = new int[idToNeighbors.get(i).size()];
                int j = 0;
                for (int neighbor : idToNeighbors.get(i)) {
                    results[i][j++] = neighbor;
                }//TODO set直接转array
            } else {
                results[i] = new int[0];
            }
        }

        return results;
    }

    public static int[] runKcore(Set<String> graph) {
        return runKcore(load(graph));
    }

    public static int[] runKcore(int[][] graph) {
        final int n = graph.length;
        final int[] upper = new int[n];
        final int[] corenesses = new int[n];
        final Set<Integer> S = new HashSet<Integer>(n);//S是未处理的点
        for (int i = 0; i < n; i++) {
            upper[i] = graph[i].length;
            S.add(i);
        }

        for (int k = 1; ; k++) {
            if (S.isEmpty())
                break;
            Set<Integer> SDel = new HashSet<Integer>();
            for (int i : S) {
                if (upper[i] < k) {
                    SDel.add(i);
                }
            }
            while (!SDel.isEmpty()) {
                final Set<Integer> SDelNew = new HashSet<Integer>();
                for (int i : SDel) {
                    for (int j : graph[i]) {
                        upper[j]--;
                        if (upper[j] == k - 1) {
                            SDelNew.add(j);
                        }
                    }

                    S.remove(i);
                    corenesses[i] = k - 1;
                }
                SDel = SDelNew;
            }
        }
        return corenesses;
    }

    public static Set<String> get2Core(Set<String> graph, Map<String, Integer> corenessesMap) {
        long start = System.currentTimeMillis();
        int[] corenesses = runKcore(graph);
        int zeroCore = 0;
        int oneCore = 0;
        Set<String> rs = new TreeSet<>(new Comparator<String>() {
            @Override
            public int compare(String o1, String o2) {
                return Integer.valueOf(o1).compareTo(Integer.valueOf(o2));
            }
        });
        for (int i = 0; i < corenesses.length; i++) {
            if (corenesses[i] > 1) {
                rs.add(String.valueOf(i));
                corenessesMap.put(String.valueOf(i), corenesses[i]);
            } else if (zeroCore == 0) {
                zeroCore++;
            } else {
                oneCore++;
            }
        }
        System.out.println("获取k-core完成:  => 一共 " + corenesses.length + "个 ,[0-core " + zeroCore + "], [1-Core " + oneCore
                + "], [2-core以上 " + rs.size() + "] ,耗时：" + (System.currentTimeMillis() - start) + "毫秒");
        return rs;
    }

    public static void exportKcore(int[] corenesses) {
        Map<Integer, Integer> rs = new HashMap<>();
        for (int i = 0; i < corenesses.length; i++) {
            rs.put(i, corenesses[i]);
        }
        System.err.println("导出结果完成...: ");

    }

    /**
     * 给一个模式的 特征core，判断模式最大的可能阶数
     */
    public static int getMaxSupportCore(List<Integer> coreArr) {
        Collections.sort(coreArr);
        Integer first = coreArr.get(0);
        while (first < coreArr.size() - 1) {
            coreArr.remove(0);
            first = coreArr.get(0);
        }
        return coreArr.size();
    }

    //获取按数字升序排序的模式
    public static String getOderString(String pattern) {
        long start = System.currentTimeMillis();
        String patternOrder = Stream.of(pattern.split(",")).sorted(new Comparator<String>() {
            @Override
            public int compare(String o1, String o2) {
                if (o1.equals("") || o2.equals("")) {
                    System.out.println(1);
                }
                return Integer.valueOf(o1).compareTo(Integer.valueOf(o2));
            }
        }).collect(Collectors.joining(","));
        costOderStringNum++;
        costOderStringTime += System.currentTimeMillis() - start;
        return patternOrder;
    }

    public static int getIndex(String[] arr, String str) {
        for (int i = 0; i < arr.length; i++) {
            if (str.equals(arr[i])) {
                return i;
            }
        }
        return -1;
    }


    public static void getKhopNeighbor() throws IOException {
        if (FileUtil.exist(SPColocation.khopNeighborPath)) {
            return;
        }
        String path = CoLocation.neighborPath;
        int numOfNodes = Integer.parseInt(path.split("_")[2]);
        int sourceNode = 0;//设置哪一个点为起始点

        List<String> neighborStrList = Files.readAllLines(Paths.get(path));
        int maxNum = 0;
        for (String str : neighborStrList) {
            String[] splits = str.split(":");
            Integer src = Integer.valueOf(splits[0]);
            if (splits.length > 1) {
                String[] neighborSplit = splits[1].split(",");
                for (String neighbor : neighborSplit) {
                    Integer trg = Integer.valueOf(neighbor);
                    maxNum = Math.max(src, maxNum);
                    maxNum = Math.max(trg, maxNum);
                }
            }
        }

        int[][] adjMatrix = new int[maxNum + 1][];
        Map<Integer, Set<Integer>> adjMap = new HashMap<>();
        for (String str : neighborStrList) {
            String[] splits = str.split(":");
            Integer starId = Integer.valueOf(splits[0]);
            if (splits.length > 1) {
                String[] neighborSplit = splits[1].split(",");
                adjMatrix[starId] = Stream.of(neighborSplit).mapToInt(Integer::parseInt).toArray();
//                adjMap.put(starId, Stream.of(neighborSplit).map(k -> Integer.parseInt(k)).collect(Collectors.toSet()));
            }
        }

        long start = System.currentTimeMillis();
        System.out.println("=> 读取邻居集存入邻接表成功，一共" + numOfNodes + "个点，最大为 " + maxNum);

//        Map<Integer, Set<Integer>> neibghorMapBySet = new HashMap<>();
//        for (int i = 0; i < maxNum + 1; i++) {
//            if (i % 2000 == 0) {
//                System.out.println("进度" + i + "/" + maxNum);
//            }
//            if (adjMap.get(i) != null) {
//                dfsMethodBySet(neibghorMapBySet, adjMap, i);
//            }
//        }
//        System.out.println("对比" + compareNum + "次");
//        System.out.println("读取完成，最大深度 " + maxDeepth + "，耗时：" + (System.currentTimeMillis() - start) + "毫秒");
//        start = System.currentTimeMillis();

        Map<Integer, String> neibghorMap = new HashMap<>();
        for (int i = 0; i < maxNum + 1; i++) {
            if (i % 2000 == 0) {
                System.out.println("进度" + i + "/" + maxNum);
            }
            if (adjMatrix[i] != null) {
                dfsMethod(neibghorMap, adjMatrix, i, maxNum);
            }
        }
        try (
                BufferedWriter khobw = new BufferedWriter(new FileWriter(SPColocation.khopNeighborPath));
        ) {
            for (Map.Entry<Integer, String> entry : neibghorMap.entrySet()) {
                String line = entry.getKey() + ":" + entry.getValue();
                khobw.write(line);
                khobw.newLine(); //换行用
            }
        }
//        System.out.println("对比" + compareNum + "次");
//        System.out.println("读取完成，最大深度 " + maxDeepth + "，耗时：" + (System.currentTimeMillis() - start) + "毫秒");


    }


    public static Map<Integer, String> dfsMethod(Map<Integer, String> neibghorMap, int[][] adjMatrix, int sourceNode, int numOfNodes) {
        //利用栈,因为需要回溯,弹出一个节点的同时把它的儿子压入栈中,把儿子弹出来时，把孙子压入栈中,所以就可以按深度了
        Stack<String> stack = new Stack();
        int[] isVisited = new int[numOfNodes + 1];

        isVisited[sourceNode] = 1;
        stack.push("0:" + sourceNode);

        if (sourceNode == 8915) {
//            System.out.println(1);
        }
        int maxDeep = Properties.k;
        while (!stack.isEmpty()) {
            String element = stack.pop();
            Integer deep = Integer.valueOf(element.split(":")[0]);
            Integer elementId = Integer.valueOf(element.split(":")[1]);
            putNeibghorMapByDeep(neibghorMap, sourceNode, elementId);
            if (sourceNode == 8915 && elementId == 13049) {
//                System.out.println(1);
            }
            if (deep != maxDeep) {//最底层
                if (adjMatrix[elementId] != null) {
                    for (int neighbor : adjMatrix[elementId]) {
//                    compareNum++;
                        if (isVisited[neighbor] == 0) {
                            isVisited[neighbor] = 1;
                            stack.push((deep + 1) + ":" + neighbor);
                        }
                    }
                }
            }
        }
//        String collect = isVisitedBitSet.toString();
//        collect = collect.substring(1,collect.length()-1);
//        String s = neibghorMap.get(sourceNode);
//        System.out.println(collect.equals(s));
        return neibghorMap;
    }

    public static void putNeibghorMapByDeep(Map<Integer, String> neibghorMapByDeep, int elementId, int neibor) {
        if (elementId == neibor) {
            return;
        }
        String neighbors = neibghorMapByDeep.get(elementId);
        if (neighbors == null) {
            neighbors = String.valueOf(neibor);
        } else {
            neighbors += "," + neibor;
        }
        neibghorMapByDeep.put(elementId, neighbors);
    }


    public static void getOldNeighborsByMakeData(edu.wlu.cs.levy.CG.KDTree<String> oldKdTree, Map<String, POI> poiMap) throws Exception {
        Long start = System.currentTimeMillis();
        int num = 1;

        try (BufferedWriter bw = new BufferedWriter(new FileWriter(CoLocation.neighborPath))) {
            for (POI star : poiMap.values()) {
                String starId = star.getId();
                String starType = star.getType();
                Double starX = star.getX();
                Double starY = star.getY();

                List<String> old_query = oldKdTree.range(
                        new double[]{Math.max(0,starX - Properties.R), Math.max(0,starY - Properties.R)},
                        new double[]{Math.min(10000,starX + Properties.R), Math.min(10000,starY + Properties.R)});
                StringBuffer starNeighborList = new StringBuffer();
                for (String neighborId : old_query) {
                    POI neighbor = poiMap.get(neighborId);
                    //排除同特征poi
                    if (!neighbor.getType().equals(starType)) {
                        //排除在矩形内，圆外的poi
                        //TODO 排除了比当前点id小的点

                        double distance = Math.sqrt((neighbor.getX() - starX) * (neighbor.getX() - starX)
                                + (neighbor.getY() - starY) * (neighbor.getY() - starY));
                        if (distance <= Properties.R) {
                            starNeighborList.append(neighbor.getId() + ",");
                        }
                    }
                }
                //去除不含邻居集，且
                if (!starNeighborList.toString().equals("")) {
                    starNeighborList.deleteCharAt(starNeighborList.length() - 1);
                    bw.write(starId + ":" + starNeighborList.toString());
                    bw.newLine(); //换行用
                }
                if (num % 5000 == 0) {
                    System.out.println("=> old kd-Tree " + num + "个节点邻居搜索完成");
                }
                num++;
            }
        } catch (Exception e) {
            System.out.println("搜索邻居 数据异常！！！");
            e.printStackTrace();
        }
        System.out.println("========4.old kd-Tree 【合成数据】  所有点邻居搜索完成，耗时: " + (System.currentTimeMillis() - start) + "毫秒");
    }

    public static edu.wlu.cs.levy.CG.KDTree<String> buildOldKDTreeByMakeData(Map<String, POI> poiMap) throws Exception {
        long start = System.currentTimeMillis();
        double maxX = 10000d;
        double minX = (double) 0;
        double maxY = 10000d;
        double minY = (double) 0;
        Random rd = new Random();

        edu.wlu.cs.levy.CG.KDTree<String> old_kdTree = new edu.wlu.cs.levy.CG.KDTree<String>(2);

        if (!FileUtil.exist(CoLocation.makedataTxtFinalPath)) {
            for (int i = 0; i < Properties.maxNum; i++) {
                String id = String.valueOf(i);
                double X = Math.abs(rd.nextDouble()) * 10000;
                double Y = Math.abs(rd.nextDouble()) * 10000;
                int type = Math.abs(rd.nextInt()) % Properties.make_featureNum;

                if (X > maxX) maxX = X;
                if (X < minX) minX = X;
                if (Y > maxY) maxY = Y;
                if (Y < minY) minY = Y;

                old_kdTree.insert(new double[]{X, Y}, id);
                POI poi = new POI(id, null, String.valueOf(type), X, Y);
                poiMap.put(id, poi);
            }
            try (BufferedWriter bw = new BufferedWriter(new FileWriter(CoLocation.makedataTxtFinalPath))) {
                for (Map.Entry<String, POI> entry : poiMap.entrySet()) {
                    POI poi = entry.getValue();
                    bw.write(poi.getId() + "," + poi.getName() + "," + poi.getType() + "," + poi.getX() + "," + poi.getY());
                    bw.newLine();
                }
            }
        }else{
            List<String> poiStrList  = Files.readAllLines(Paths.get(CoLocation.makedataTxtFinalPath));
            for (String poiStr : poiStrList) {
                String[] poiInfo = poiStr.split(",");
                String id = poiInfo[0];
                String name = poiInfo[1];
                String type = poiInfo[2];
                Double X = Double.valueOf(poiInfo[3]);
                Double Y = Double.valueOf(poiInfo[4]);

                POI poi = new POI(id, name, type, X, Y);
                poiMap.put(id, poi);
                old_kdTree.insert(new double[]{X, Y}, id);
            }
        }
        System.out.println("========3.old kd-Tree建树 【合成数据】完成，耗时: " + (System.currentTimeMillis() - start) + "毫秒，树节点共"
                + old_kdTree.size() + "个   => 覆盖范围： (" + minX + "," + minY + ")" + "(" + maxX + "," + maxY + ")");
        return old_kdTree;
    }

} 